package com.ihr360.payroll.service.salarytasklet;

import com.google.common.collect.Lists;
import com.ihr360.authority.sharing.privilege.GetOnePrivilege;
import com.ihr360.authority.sharing.valid.AuthValid;
import com.ihr360.authority.sharing.valid.EnableAuthValidation;
import com.ihr360.authority.sharing.validator.GetOneValidator;
import com.ihr360.commons.context.session.Ihr360Session;
import com.ihr360.commons.context.session.Ihr360SessionContextHolder;
import com.ihr360.commons.entity.AuthUserInfo;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.context.SpringContext;
import com.ihr360.job.core.BatchStatus;
import com.ihr360.job.core.Job;
import com.ihr360.job.core.JobParameter;
import com.ihr360.job.core.JobParameters;
import com.ihr360.job.core.entity.JobExecution;
import com.ihr360.job.core.entity.JobInstance;
import com.ihr360.job.core.lanuch.JobLauncher;
import com.ihr360.job.core.repository.JobExecutionAlreadyRunningException;
import com.ihr360.job.core.repository.JobInstanceAlreadyCompleteException;
import com.ihr360.payroll.dao.ActivePayPeriodRepository;
import com.ihr360.payroll.dao.SalaryPlanRepository;
import com.ihr360.payroll.dao.SalaryPlanStaffRepository;
import com.ihr360.payroll.model.dto.salary.plan.SalaryPlan;
import com.ihr360.payroll.model.dto.salary.plan.SalaryPlanStaff;
import com.ihr360.payroll.model.dto.salary.task.period.ActivePayPeriod;
import com.ihr360.tasklet.JobExecutionOverview;
import com.ihr360.validation.ValidationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 薪资计算主流程任务
 *
 * @author davidwei
 */
@Service
public class SalaryTaskletServiceImpl implements SalaryTaskletService {
    private static final Logger logger = LoggerFactory.getLogger(SalaryTaskletService.class.getName());

    private final static class TASKLET_PARAM {
        public static final String COMPANY = "company";
        public static final String SALARY_PLAN = "salaryPlan";
        public static final String SALARY_PERIOD = "salaryPeriod";
        public static final String YEAR = "year";
        public static final String MONTH = "month";
        public static final String TERM = "salaryPeriodTerm";
        public static final String SEQ = "sequence";
    }

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    SalaryPlanRepository salaryPlanRepository;

    @Autowired
    SalaryPlanStaffRepository salaryPlanStaffRepository;

    @Autowired
    ActivePayPeriodRepository activePayPeriodRepository;

    @Override
    @EnableAuthValidation
    public JobExecutionOverview launchPreDataAndCalcJob(@AuthValid(validator = GetOneValidator.class, entity = SalaryPlan.class) Long salaryPlanId, Long periodId, Long termId, List<String> staffIdList,
                                                        Integer year, Integer month, int seq) throws JobExecutionAlreadyRunningException {
        Validate validate = new Validate(salaryPlanId, periodId, termId, year, month, seq, staffIdList).invoke();
        SalaryPlan salaryPlan = validate.getSalaryPlan();
        ActivePayPeriod activePayPeriod = validate.getActivePayPeriod();
        Map<String, JobParameter> parameterMap = validate.getParameterMap();

        try {
            logger.info(Thread.currentThread().getName());
            SalaryCalcJob salaryCalcJob = getSalaryCalcJobBean();
            Job job = salaryCalcJob.salaryCalculationJob(salaryPlan, activePayPeriod, staffIdList);
            JobExecution execution = jobLauncher.run(job, new JobParameters(parameterMap));
            validateExecutionStatus(execution);
            JobExecutionOverview overview = new JobExecutionOverview(execution.getJobInstance().getJobName(), execution.getJobInstance().getInstanceId(), execution.getId());
            return overview;
        } catch (JobExecutionAlreadyRunningException e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
            throw e;
//            throw new Ihr360Exception(null, "该任务已经在进行中，你可以通过强制重新计算来重新开始任务");
        } catch (JobInstanceAlreadyCompleteException e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
            throw new Ihr360Exception(null, "该任务已完成");
        } catch (Ihr360Exception e) {
            throw e;
        } catch (Exception e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
        }
        return null;
    }

    private void validateExecutionStatus(JobExecution execution) {
        BatchStatus status = execution.getStatus();
        if (BatchStatus.FAILED.equals(status)) {
            throw new Ihr360Exception(null, "计算任务执行繁忙,请稍后再试");
        } else if (BatchStatus.STOPPED.equals(status)) {
            throw new Ihr360Exception(null, "计算任务已停止");
        } else if (BatchStatus.ABANDONED.equals(status)) {
            throw new Ihr360Exception(null, "计算任务被抛弃");
        }
    }

    private SalaryCalcJob getSalaryCalcJobBean() {
        return SpringContext.getBean(SalaryCalcJob.class);
    }

    @Override
    @EnableAuthValidation
    public JobExecutionOverview launchPreDataJob(@AuthValid(validator = GetOneValidator.class, entity = SalaryPlan.class)Long salaryPlanId, Long periodId, Long termId, List<String> staffIdList,
                                                 Integer year, Integer month, int seq) throws JobExecutionAlreadyRunningException {
        Validate validate = new Validate(salaryPlanId, periodId, termId, year, month, seq, staffIdList).invoke();
        SalaryPlan salaryPlan = validate.getSalaryPlan();
        ActivePayPeriod activePayPeriod = validate.getActivePayPeriod();
        Map<String, JobParameter> parameterMap = validate.getParameterMap();

        try {
            logger.info(Thread.currentThread().getName());
            SalaryCalcJob salaryCalcJob = getSalaryCalcJobBean();
            Job job = salaryCalcJob.salaryPreCalcJob(salaryPlan, activePayPeriod, staffIdList);
            JobExecution execution = jobLauncher.run(job, new JobParameters(parameterMap));
            validateExecutionStatus(execution);
            JobExecutionOverview overview = new JobExecutionOverview(execution.getJobInstance().getJobName(), execution.getJobInstance().getInstanceId(), execution.getId());
            return overview;
        } catch (JobExecutionAlreadyRunningException e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
            throw e;
//            throw new Ihr360Exception(null, "该任务已经在进行中，你可以通过强制重新计算来重新开始任务");
        } catch (JobInstanceAlreadyCompleteException e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
            throw new Ihr360Exception(null, "该任务已完成");
        } catch (Ihr360Exception e) {
            throw e;
        } catch (Exception e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
        }
        return null;
    }

    @Override
    @EnableAuthValidation
    public JobExecutionOverview launchCalcJob(@AuthValid(validator = GetOneValidator.class, entity = SalaryPlan.class)Long salaryPlanId, Long periodId, Long termId, List<String> staffIdList,
                                              Integer year, Integer month, int seq) throws JobExecutionAlreadyRunningException {
        Validate validate = new Validate(salaryPlanId, periodId, termId, year, month, seq, staffIdList).invoke();
        SalaryPlan salaryPlan = validate.getSalaryPlan();
        ActivePayPeriod activePayPeriod = validate.getActivePayPeriod();
        Map<String, JobParameter> parameterMap = validate.getParameterMap();

        try {
            logger.info(Thread.currentThread().getName());
            SalaryCalcJob salaryCalcJob = getSalaryCalcJobBean();
            Job job = salaryCalcJob.salaryCalcWithPreDataJob(salaryPlan, activePayPeriod, staffIdList);
            JobExecution execution = jobLauncher.run(job, new JobParameters(parameterMap));
            validateExecutionStatus(execution);
            JobExecutionOverview overview = new JobExecutionOverview(execution.getJobInstance().getJobName(), execution.getJobInstance().getInstanceId(), execution.getId());
            return overview;
        } catch (JobExecutionAlreadyRunningException e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
            throw e;
//            throw new Ihr360Exception(null, "该任务已经在进行中，你可以通过强制重新计算来重新开始任务");
        } catch (JobInstanceAlreadyCompleteException e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
            throw new Ihr360Exception(null, "该任务已完成");
        } catch (Ihr360Exception e) {
            throw e;
        } catch (Exception e) {
            if (logger.isWarnEnabled()) {
                logger.error(e.getMessage(), e);
            }
        }
        return null;
    }

    @Override
    public JobInstance forceRestartJob(Long salaryPlanId, Long periodId, Long termId, Integer year, Integer month, int seq) {
        return null;
    }

    private class Validate {
        private Long salaryPlanId;
        private Long periodId;
        private Long termId;
        private Integer year;
        private Integer month;
        private int seq;
        private Map<String, JobParameter> parameterMap;
        private SalaryPlan salaryPlan;
        private ActivePayPeriod activePayPeriod;
        private List<String> staffIdList;

        public Validate(Long salaryPlanId, Long periodId, Long termId, Integer year, Integer month, int seq, List<String> staffList) {
            this.salaryPlanId = salaryPlanId;
            this.periodId = periodId;
            this.termId = termId;
            this.year = year;
            this.month = month;
            this.seq = seq;
            this.staffIdList = staffList;
        }

        public Map<String, JobParameter> getParameterMap() {
            return parameterMap;
        }

        public SalaryPlan getSalaryPlan() {
            return salaryPlan;
        }

        public ActivePayPeriod getActivePayPeriod() {
            return activePayPeriod;
        }

        public Validate invoke() {
            Calendar calendar = Calendar.getInstance();
            ValidationUtils.rejectIfEmpty(salaryPlanId, null, "必须提供有效的方案标识信息");
            ValidationUtils.rejectIfEmpty(periodId, null, "必须提供有效的账期标识信息");
            ValidationUtils.rejectIfEmpty(termId, null, "必须提供有效的账期批次信息");
            ValidationUtils.rejectNotInRange(year, calendar.get(Calendar.YEAR) - 1, calendar.get(Calendar.YEAR) + 1, null, "计算任务年份信息不在合理范围内");
            ValidationUtils.rejectNotInRange(month, calendar.getMinimum(Calendar.MONTH) + 1, calendar.getMaximum(Calendar.MONTH) + 1, null, "薪资账期月份信息必须在1-12之间");

            String companyId = Ihr360SessionContextHolder.getCompanyId();

            ValidationUtils.rejectIfEmpty(companyId, null, "无法获得公司标识");

            Ihr360Session session = Ihr360SessionContextHolder.getSession();

            ValidationUtils.rejectIfEmpty(session, null, "无法获得会话信息");
            AuthUserInfo userInfo = session.getUserInfo();
            ValidationUtils.rejectIfEmpty(session, null, "无法获得员工会话信息");

            //设置薪资计算任务标志参数
            parameterMap = new HashMap<>(8);
            parameterMap.put(JobExecution.JOB_OWNER_ID, new JobParameter(companyId));
            parameterMap.put(JobExecution.JOB_OWNER_NAME, new JobParameter(userInfo.getCompanyName()));
            parameterMap.put(TASKLET_PARAM.SALARY_PLAN, new JobParameter(salaryPlanId));
            parameterMap.put(TASKLET_PARAM.SALARY_PERIOD, new JobParameter(periodId));
            parameterMap.put(TASKLET_PARAM.YEAR, new JobParameter(Long.valueOf(year)));
            parameterMap.put(TASKLET_PARAM.MONTH, new JobParameter(Long.valueOf(month)));
            parameterMap.put(JobExecution.OWNER_JOB_ID, new JobParameter(termId));
            parameterMap.put(TASKLET_PARAM.SEQ, new JobParameter(Long.valueOf(seq)));

            //获取薪资方案并验证
            salaryPlan = salaryPlanRepository.findOne(salaryPlanId);
            ValidationUtils.rejectIfEmpty(salaryPlan, null, "无法获取薪资方案信息");

            //获取账期
            activePayPeriod = activePayPeriodRepository.findOne(periodId);

            ValidationUtils.rejectIfEmpty(activePayPeriod, null, "无法获取账期信息");
            ValidationUtils.rejectIfNotEqual(activePayPeriod.getCompanyId(), companyId, null, "公司不存在此账期");
            ValidationUtils.rejectIfEmpty(activePayPeriod.getCurrentTerm(), null, "账期的当前计算批次还未设定");
            ValidationUtils.rejectNotEquals(termId, activePayPeriod.getCurrentTerm().getId(), null, "将要计算的账期批次和账期的当前批次不同");
            int staffCnt = salaryPlanStaffRepository
                    .countBySalaryPlanIdAndEnabledAndStaffStateIn(salaryPlanId, Boolean.TRUE,
                            Lists.newArrayList(SalaryPlanStaff.StaffState.NORMAL, SalaryPlanStaff.StaffState.TEMP_ADD));
            if (CollectionUtils.isEmpty(staffIdList) && staffCnt == 0) {
                ValidationUtils.reject(null, "当前计算任务中不包含任何员工数据");
            }
            return this;
        }
    }
}